# hw03/question2/modAddablePolynomial.py          David MacQuigg ece373 11/17/07
'''Class to manipulate and display univariate polynomials.  Operations include
add two polynomials and negate a polynomial.
'''
import copy

if __name__ == '__main__':  # Set up for package imports
    from packageSetup import setPackagePath
    setPackagePath('hw03.question2')

from distribution.hw03.modMonomial   import Monomial
from distribution.hw03.modPolynomial import Polynomial
from distribution.hw03.modAddable    import Addable
from distribution.hw03.modAddableInteger import AddableInteger, Integer

class AddablePolynomial(Polynomial, Addable):
    '''Extend Polynomial to implement the methods in Addable, including __add__
and __neg__.  __str__ in Addable is shadowed by __str__ in Polynomial.  # [1]
    '''

    def __neg__(self):
        '''Return the negative of this polynomial, leaving the original
unaltered.

    >>> print -(apoly1 + poly2)
    -2.0x^55 - 1.2x^4 + 2.1x^3 - 1.1
    >>> print apoly1 + (-apoly2)        # __sub__ not yet implemented
    -1.4x^55 + 1.2x^4 + 2.1x^3 - 7.3
    >>> print apoly1 - apoly2
    Traceback (most recent call last):
     - - -
    TypeError: unsupported operand type(s) for -: 'AddablePolynomial' and 'AddablePolynomial'
        '''
        newPoly = copy.deepcopy(self)
        
        for exp in self._getSortedExponents():                   # [0, 4, 55]
            
            oldC = self.getCoefficientByExponent(exp)
            newC = -oldC
            newPoly.setCoefficientByExponent(exp, newC)

        return newPoly
    
    def __add__(self, poly2):
        '''Add to self the elements of poly2, creating new elements if
necessary, just adding coefficients where possible.  Return a new polynomial,
leaving the original polynomials unaltered.  Treat an AddableInteger as a
special type of Polynomial.  If poly2 is anything but a Polynomial or
AddableInteger, return None.

    >>> print apoly1; print poly2
    0.3x^55 + 1.2x^4 - 3.1
    1.7x^55 - 2.1x^3 + 4.2
    >>> print apoly1 + poly2  # add a Polynomial
    2.0x^55 + 1.2x^4 - 2.1x^3 + 1.1
    >>> print apoly1 + ai1    # add an AddableInteger
    0.3x^55 + 1.2x^4 - 2.1
    >>> print apoly1 + 1      # add a normal integer
    None
        '''
        # Build a dict with values to be added
        polyDict2 = {}                             # {55: 1.7, 3: -2.1, 0: 4.2}
        if isinstance(poly2, Polynomial):
            for exp in poly2._getSortedExponents():                # [55, 3, 0]
                polyDict2[exp] = poly2.getCoefficientByExponent(exp)
        elif isinstance(poly2, AddableInteger):
            polyDict2[0] = poly2.getInteger().intValue()
        else:
            return None  # signal for an invalid type of poly2

        # Make a clone of the original object
        clone = copy.deepcopy(self)

        # Add the new values to the clone
        for exp in polyDict2:                                           # [2]
            addVal = polyDict2[exp]

            oldC = self.getCoefficientByExponent(exp)
            newC = oldC + addVal
            clone.setCoefficientByExponent(exp, newC)

        return clone


if __name__ == '__main__':

    # sample data for testing
    polyDict1 = {55: 0.3, 4: 1.2, 0: -3.1}
    apoly1 = AddablePolynomial(polyDict1)
    polyDict2 = {55: 1.7, 3: -2.1, 0: 4.2}
    poly2 = Polynomial(polyDict2)
    apoly2 = AddablePolynomial(poly2)

    ai1 = AddableInteger(1)

    def unittests():
        '''Extra tests here.  Tests from the HW assignment.

        >>> poly1 = Polynomial({3: 3.0, 1: 0.5})
        >>> print "poly1 =", poly1
        poly1 = 3.0x^3 + 0.5x
        >>> poly2 = Polynomial({5: 5.0, 3: 3.0, 1: -0.5})
        >>> print "poly2 =", poly2
        poly2 = 5.0x^5 + 3.0x^3 - 0.5x
        >>> addablePoly1 = AddablePolynomial(poly1)
        >>> poly3 = addablePoly1 + poly2
        >>> print "(%s) + (%s) = %s" % (addablePoly1, poly2, poly3)
        (3.0x^3 + 0.5x) + (5.0x^5 + 3.0x^3 - 0.5x) = 5.0x^5 + 6.0x^3
        '''
    from doctest import testmod
    testmod(verbose=True)

''' Notes
[1] __str__() method is defined in both Polynomial and Addable.  Python's MRO
(Method Resolution Order) says the Polynomial method will be used, since
Polynomial is listed first in the AddablePolynomial class declaration.

[2] Alternative for loops:

##        iterator = polyDict2.iterkeys()              # like Java
##        for exp in iterator:
##            addVal = polyDict2[exp]

##        for exp, addVal in polyDict2.iteritems():    # all in one line
'''
